C++ on MSVC講習/反復文 - whileほわいる/forふぉー

あらすじと概要

前回は条件分岐2回目ということで、switch文を解説しました。
今回は、同じような動作を反復することで、短いコードに収める文たちを解説します。

重要語

goto文

実行すると、対応するラベルに処理が移動する文

ラベル

switch文やgoto文などから移動する時の目印

ループ

同じ文を複数回実行する部分

複合代入演算子

@を二項演算子として、a @= bの形で、a = a @ bを行う演算子

+=

複合加算代入演算子

-=

複合減算代入演算子

*=

複合乗算代入演算子

/=

複合除算代入演算子

%=

複合剰余代入演算子

while文

条件がtrueである限りを実行し続ける文

do文(do-while文とも)

無条件に1回を実行してから、条件式がtrueである限りを実行し続ける文

for文

初期化文反復式が追加されて使いやすいwhile文

break文

switch文、while文、do文、for文を終了させる文

continue文

while文、do文、for文の実行を終了させる文

ループカウンタ

ループでループの回数を数える時に使っている変数

++

インクリメント演算子、整数の変数の値を1増やす

--

デクリメント演算子、整数の変数の値を1減らす

goto文

goto文は、C++よりもより低いレイヤーでも存在する非常に機械よりの文です。
switch文で触れたように、goto文を実行すると、対応するラベルに処理が移動します。
goto文
#include <iostream>

int main()
{
    goto A;
    std::cout << "飛ばされる";
A:
    int a = 0;
BACK:
    {
        std::cout << a << "\n";
        a += 1;
    }
    if (a < 10) goto BACK;
}

実行結果
0
1
2
3
4
5
6
7
8
9

解説

解説です。

ラベル

ラベル名:ころんを1個以上文の頭に付けて文をラベル付けすることが出来ます。
ラベルはgoto文から移動する先の文を示し、順次実行時には何も起こしません。
goto文で使用するラベルは、識別子のルールに従って命名する必要があります。
ラベルは唯一、関数単位でのみスコープを持っており、他の識別子とは別で管理されます。
つまり、「同じ関数の中」でのみ、同じラベルを複数の文に付けてはいけないのです。
ラベル
ラベル名 :

goto文の効果

goto文は、以下のように記述し、指定したラベルへと直接処理を移します。
つまり、実行されたgoto文から指定したラベルの間にある文は、実行されないのです。
ただし、goto文は同じ関数内でのみ処理を移すことが出来、関数を超えることは出来ません。
goto文
goto ラベル名 ;

goto文の制限

直接処理を移してしまうgoto文は非常に強力ですが、多用はコードが読みづらくなります。
それはさておき、goto文は変数のスコープに入ってはいけないという制限もあります。
つまり、ある変数の宣言を実行しないまま、その変数が存在する文に入ってはいけないのです。
例えばgoto Z; { int i = 0; Z:; }だと、宣言を飛び越えているのでダメです。

goto文の制限の例外

ただし、今説明できないものを含め、いくつかの場合にはその制限が無いことがあります。
例えば、組み込み型の場合において初期化子がない場合は制限が適用されません。
ただ、goto文を使った方が簡潔になる機会がほぼ無いので、必ず制限を守ればいいでしょう。

goto文の制限の逆

さて、制限は変数のスコープに入る事でしたが、逆に出る時はどうなるのでしょうか。
変数のスコープから出る場合は、ライフタイムが終了した変数を適切に処理してくれます。
言い換えれば、通常通りに変数のスコープから抜けた時と同様の後処理をしてくれます。
特に気にする必要はありません。

goto文とif文でループ

同じ文を複数回実行する部分のことをループと言い、while/for文はループの文です。
goto文とif文を用いると、良いものでは無いですが、while/for文と同じことが出来ます。
サンプルコードのように、ラベルを置いて、if文でgoto文を包んでループ出来ます。
サンプルコードでは、変数aを1周ごとに1増やしていて、09の間ループしますね。

複合代入演算子

さて、a += 1;a1足しているであろうことは分かると思います。
この+=などの=の前に何らかの二項演算子がくっついたものを、複合代入演算子と言います。
右辺の値を左辺に「何らかの二項演算子」で処理して左辺に代入するのです。
なので、a += 1;は、a = a + 1;と同じことで、a1足した物をaに代入しています。
即ち、a1足している、ということですね。

複合代入演算子(今までに解説した二項演算子の中で存在するもの)

+=

加算代入

-=

減算代入

*=

乗算代入

/=

除算代入

%=

剰余代入

while文

while文は、先述の通り、ループを書くための文です。
goto文とif文を使うより単純に書くことが出来ます。
while文
#include <iostream>
#include <string>
#include <algorithm>

int main()
{
    int n; std::cin >> n;
    while (n /= 10)
    {
        std::cout << n << "\n";
    }
    std::cout << "\n";

    while (std::cin >> n)
    {
        if (n > -10) { std::cout << "next : "; continue; }
        if (n < 20) { break; }
    }
    std::cout << "\n";

    std::string s = "abc";
    do
    {
        std::cout << s << "\n";
    } while (std::next_permutation(s.begin(), s.end()));
}

実行結果例1
$ 1

$ -20

abc
acb
bac
bca
cab
cba

実行結果例2
$ 11
1

$ 0
$ next : -10

abc
acb
bac
bca
cab
cba

実行結果例3
$ 11111111
1111111
111111
11111
1111
111
11
1

$ 50
$ next : -50

abc
acb
bac
bca
cab
cba

解説

解説です。

whileほわいる文とdoどぅー文(do-while文)

while文は、条件がtrueである間、を実行し続ける文です。
do文は、が無条件で1度実行された後、while文と同じ動作になる文です。
do文は規格での表記に従っていますが、普通はdo-while文などと呼ばれます。
while文
while ( 条件 ) 
do文
do 文 while ( 条件式 ) ;

条件

条件はif文と同様、boolに評価できる式や、boolに変換できる型の変数宣言が出来ます。
条件で宣言された変数は、次に条件が評価される時には破棄され、また宣言されます。
なお、条件で宣言された変数は、やはりwhile文全体をスコープに持ちます。

条件式

一方で、条件式は名前の通り、boolに変換できる式のみを書け、宣言は書けません。
そして、は、if文やswitch文などと同様に、やはり1つの文で、複合文がほとんどです。

while文とdo文の実行のされ方

while文とdo文は、その動きを複合文とif文とgoto文に置き換えることで説明出来ます。
次のような模擬コード(構文の名前はこれで表記)に置き換えることが出来ます。
while文を置き換えると(模擬コード)
label:
{
    if ( 条件 )
    {
        
        goto label;
    }
}
do文を置き換えると(模擬コード)
label:
{
    
    if ( 条件式 ) goto label;
}

breakぶれいく文、continueこんてぃにゅー

break文はswitch文の時と同様に、while/do文でもwhile/do文を終了させます。
continue文は、実行されるとループの最後へと実行を移し、の実行が終了します。
ただし両方とも、while文がネストしている時には、一番内側のwhile文を終了させます。

break文とcontinue文を疑似的に表現すると

break/continue文の移動先をbreak/continueラベルと、疑似的に表現して、
先程の疑似コードに組み込んだものと、while/do文に組み込んだものは以下です。
while文のbreak/continue文の行先(模擬コード)
label:
{
    if ( 条件 )
    {
        
        continue:
        goto label;
    }
}
break:
do文のbreak/continue文の行先(模擬コード)
label:
{
    
    continue:
    if ( 条件式 ) goto label;
}
break:
while文で表現すると(模擬コード)
while ( 条件 )
{
    
    continue:
}
break:
do文で表現すると(模擬コード)
do
{
    
    continue:
} while ( 条件式 ) ;
break:

1つ目のwhile

1つ目のwhile文は、入力した整数をどんどん10で割って、0になると終了します。
つまり、入力した整数の桁数-1がループする回数になるということですね。

std::cinがboolに評価出来るということ

std::cinがboolに評価できる事に驚いた人もいるかもしれません。
std::cinは、入力が出来る時にtrue、入力が出来ないときにfalseに評価されます。
今は標準入力の内容は分かっていると思うので、チェックする必要は無いでしょう。

条件どうなってるの

2つ目のwhile文の中のif文は、一見すると分かりづらくなっています。
が、よくよく整理すると次のようになり、とても簡単ですね。

2つ目のwhile文

n >= -9

continue

n <= -10

break

std::next_permutation

std::next_permutationは、順列を生成するアルゴリズムが実装された関数です。
#include <algorithm>を書くと使うことが出来るようになります。
詳しくは今後ですが、現在までに解説した型で使用できるのは、std::stringだけです。
今回のサンプルコードでの効果は、sの中身が、辞書順で次の順列に書き換えられます。

順列とは

順列は数学で学ぶ考え方ですが、異なるいくつかのものを順序付けして並べることです。
例えば、1,2,3という列があれば、これの並べ方は以下のように6通りあります。
1,2,3/1,3,2/2,1,3/2,3,1/3,1,2/3,2,1
ここで、この6通りは辞書順に並んでいますが、1つ次(右)の列(一番右なら一番左へ)
に変えるのがstd::next_permutationのすることだ、という風に言うことが出来ます。

for文

for文は、whileの上位互換です。更に簡潔に書くことが出来ます。
for文
#include <iostream>

int main()
{
    int n;
    for (;std::cin >> n;)
    {
        if (n > 0) break;
        std::cout << "自然数(1以上の整数)を入力してください\n";
    }

    for (int i = 0; i < n; ++i)
    {
        std::cout << i << " ";
        continue;
    }
    std::cout << "\n";

    for (int i = 1; i < 10; ++i)
    {
        for (int j = 1; j < 10; j++)
        {
            if (i * j > 30) break;
            std::cout << i * j << "\t";
        }
        std::cout << "\n";
    }
}

実行結果例
$ 30
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
1       2       3       4       5       6       7       8       9
2       4       6       8       10      12      14      16      18
3       6       9       12      15      18      21      24      27
4       8       12      16      20      24      28
5       10      15      20      25      30
6       12      18      24      30
7       14      21      28
8       16      24
9       18      27

解説

解説にいきます。

for文

for文は、while文に初期化文反復式を付け加えたものです。
やはり条件がtrueである間、を実行し続ける文です。
基本的にはwhile文と似ていますが、do-for文みたいなのはありません。
for文
for ( 初期化文 条件 ; 反復式 ) 
正確さを無くしてわかりやすくしたfor文
for ( 宣言か式 ; 宣言か式 ; 式 ) 

初期化文

初期化文はif文やswitch文で出てきたもので、むしろfor文が元です。
;で終わる変数などの宣言、式文や;だけの文である空文などが書けます。
そして、やはりここで宣言された変数は、for文全体をスコープに持ちます。

条件

条件もif文やwhile文で出てきたものと全く同じものになっています。
boolに評価できる式や、boolに変換することが出来る型の変数の宣言が書けます。
また、条件で宣言された変数も、やはりfor文全体をスコープに持ちます。
ただし、for文の条件は省略すると、trueとして扱われる事には注意しましょう。
も、やはり今までのものと同様で、複合文が好まれて使われます。

反復式とfor文の実行のされ方

反復式には、任意の式を書くことが出来ます。
反復式が実行された後、条件が評価される前に評価されます。
つまり、for文は以下のような疑似コードで表現することが出来ます。
for文をwhile文で表現すると(疑似コード)
{
    初期化文
    while ( 条件 )
    {
        
        反復式 ;
    }
}
for文をgoto文レベルで表現すると(疑似コード)
{
    初期化文
    label:
    {
        if ( 条件 ) {
            
            反復式 ;
            goto label;
        }
    }
}

break文、continue文

やっぱり、break文とcontinue文もwhile文と同じ挙動を示します。
break文はfor文を終わらせ、continue文はループの最後へ移動、を終了させます。
ただし、continue文が実行された時も、反復式は実行されることに留意しましょう。
また、for文がネストしていたら、一番内側のfor文を終了させるのも同じです。

break文とcontinue文を疑似的に表現すると

break/continue文の移動先をbreak/continueラベルと、疑似的に表現して、
先程の疑似コードに組み込んだものと、for文に組み込んだものは以下です。
for文のbreak/continue文の行先1(疑似コード)
for ( 初期化文 条件 ; 反復式 )
{
    
    continue:
}
break:
for文のbreak/continue文の行先2(疑似コード)
{
    初期化文
    while ( 条件 )
    {
        
        continue:
        反復式 ;
    }
    break:
}
for文のbreak/continue文の行先3(疑似コード)
{
    初期化文
    label:
    {
        if ( 条件 ) {
            
            continue:
            反復式 ;
            goto label;
        }
    }
    break:
}

ループカウンタとイン/デクリメント

初期化文で宣言し、ループを制御する変数を、ループカウンタと呼ぶことがあります。
ほとんどの場合、ループカウンタは整数型の変数で、識別子にiが使われます。
そして、反復式ではループカウンタに1を足し引きすることが多いので、演算子があります。
1足すのがインクリメント演算子、1引くのがデクリメント演算子で、前置と後置があります。
後置のイン/デクリメント演算子は、落とし穴があるので、単独の式で使用するべきでしょう。

インクリメントとデクリメント演算子(整数の変数をnとして)

++n

前置インクリメント、n += 1と同じ

n++

後置インクリメント、n += 1と類似

--n

前置デクリメント、n -= 1と同じ

n--

後置デクリメント、n -= 1と類似

1つ目のfor文

int n;は、このfor文に入れてしまうと、後のfor文で使えないので前に書きます。
std::cin >> n条件に置いています。反復式に置くとマズいのがわかるでしょうか。
反復式に置くと、変数nを未初期化で読み取ってしまうので、未定義動作になりますね。

2つ目のfor文(for文でn回ループしたい)

for文は、初期化文反復式を持っていることから、n回ループしたい時によく使います。
特に、0からn-1n回ループすることが往々にしてあるため、書き方に慣れましょう。
continue文が申し訳程度にありますが、continue文の後に何もないので意味はないです。

3/4つ目のfor文(2重ループしたい)

for文の中にfor文を入れれば、ループさせる回数を掛け算のように増やすことが出来ます。
両方のfor文で、ループカウンタを1から9まで回せば、九九表を作ることが出来ますね。
2重ループする時には、外側と内側のループカウンタの名前を被らせないようにしましょう。
被らせると、整数と小数の回で解説したように、外側のループカウンタが隠れてしまいます。
なお、今回は30よりも大きい値は表示しないようにしたので、右下が消えています。

練習問題

今回は、フィボナッチ数列に関した問題にしましょう。

問題文

フィボナッチ数列のn番目の項は何ですか。
フィボナッチ数列とは、1, 1, 2, 3, 5, 8, 13, 21, 34のように、
第1項、第2項が1で、それ以外の項は1/2個前の項の和であるような、言い換えれば、
fib(1) = 1, fib(2) = 1, fib(N) = fib(N-1) + fib(N-2)である数列のことです。
入力
$ N
制約
1 <= N <= 93, Nは整数
出力
フィボナッチ数列のN項目を出力

入出力例1
$ 1
1

入出力例2
$ 2
1

入出力例3
$ 50
12586269025

入出力例4
$ 93
12200160415121876738

入出力例の注意

出力すべき値は、intを超えるような値になることがあります。
また、long longをも超えるような値になることがあります。
ヒント1 使う変数は、入力用変数、ループカウンタ、それに加えて少なくとも2つ変数が必要です。
回答例では、入力用変数、ループカウンタ、それに加えて3つの変数を使用しています。
ヒント2 現在の項と1つ前の項を保持する変数を用意してループしてもいいですね。
あるいは、答え変数と2つの変数を用意して、現在の項をとりあえず答え変数にいれてしまい、
そのあと、2つの変数の小さい方(古い方)に代入してしまってループするのでもいいですね。
回答例
回答例
#include <iostream>

int main()
{
    int n;
    std::cin >> n;

    unsigned long long a = 1, b = 1, ans = 1;
    for (int i = 2; i < n; i++)
    {
        ans = a + b;
        if (i % 2 == 0)
        {
            a = ans;
        }
        else
        {
            b = ans;
        }
    }

    std::cout << ans << "\n";
}

N番目の値が何かの一覧
N番目の値が何かの一覧
1 1
2 1
3 2
4 3
5 5
6 8
7 13
8 21
9 34
10 55
11 89
12 144
13 233
14 377
15 610
16 987
17 1597
18 2584
19 4181
20 6765
21 10946
22 17711
23 28657
24 46368
25 75025
26 121393
27 196418
28 317811
29 514229
30 832040
31 1346269
32 2178309
33 3524578
34 5702887
35 9227465
36 14930352
37 24157817
38 39088169
39 63245986
40 102334155
41 165580141
42 267914296
43 433494437
44 701408733
45 1134903170
46 1836311903
47 2971215073
48 4807526976
49 7778742049
50 12586269025
51 20365011074
52 32951280099
53 53316291173
54 86267571272
55 139583862445
56 225851433717
57 365435296162
58 591286729879
59 956722026041
60 1548008755920
61 2504730781961
62 4052739537881
63 6557470319842
64 10610209857723
65 17167680177565
66 27777890035288
67 44945570212853
68 72723460248141
69 117669030460994
70 190392490709135
71 308061521170129
72 498454011879264
73 806515533049393
74 1304969544928657
75 2111485077978050
76 3416454622906707
77 5527939700884757
78 8944394323791464
79 14472334024676221
80 23416728348467685
81 37889062373143906
82 61305790721611591
83 99194853094755497
84 160500643816367088
85 259695496911122585
86 420196140727489673
87 679891637638612258
88 1100087778366101931
89 1779979416004714189
90 2880067194370816120
91 4660046610375530309
92 7540113804746346429
93 12200160415121876738

参照、出典

参照や出典です

参照

[stmt.label]

https://timsong-cpp.github.io/cppwp/n4861/stmt.label

[stmt.jump]

https://timsong-cpp.github.io/cppwp/n4861/stmt.jump#stmt.goto

[stmt.iter]

https://timsong-cpp.github.io/cppwp/n4861/stmt.iter

文 - cppreference.com

https://ja.cppreference.com/w/cpp/language/statements

goto 文 - cppreference.com

https://ja.cppreference.com/w/cpp/language/goto

代入演算子 - cppreference.com

https://ja.cppreference.com/w/cpp/language/operator_assignment

while ループ - cppreference.com

https://ja.cppreference.com/w/cpp/language/while

std::basic_ios<CharT,Traits>::operator bool - cppreference.com

https://ja.cppreference.com/w/cpp/io/basic_ios/operator_bool

for ループ - cppreference.com

https://ja.cppreference.com/w/cpp/language/for